MALHAWK Logo

MalHawk

Reverse Engineering
PLAT:Unix/Linux
DIFF:2.0
QUAL:3.7
2026-06-21

find license key

Create a keygen to solve this crackme. No patching allowed. Run as ./findlicensekey <username> (username can be of your choice). Good luck!

This binary requires me to get the license key of the program, and this varies based on the username entered. It has not been defined anywhere that the username needs to be of a certain length but looking at the keygen function, there is a loop running 23 times through the username entered and generating a 24 character key, hence our username is supposed to be 24 characters long. We will get to see later what happens when we do not enter a username worth 24 characters long.

Software license keys vary in format and length depending on the vendor, but commonly vary between 24 to 25 characters long. They are generated using mathematical algorithms and may be tied to user-specific information (such as a username or machine ID) or simply encode license information that can be verified using checksums or digital signatures.

alt text

The binary takes two arguments on startup, the program name and the username. The keygen function sub_555555555189 is called immediately, and the generated key sits in rbp+s2 by the time scanf runs.

alt text

The generated key is stored in rbp+s2 and the key we entered is in rbp+s1. If they match, ZF is set and Key validated is printed. Otherwise the Invalid key path is taken.

The Keygen Function

alt text

This is where the key is built, one character at a time. The loop counter lives in var_18, starting at zero. Every iteration runs the following sequence:

The counter is loaded into eax and movsxd sign-extends it into rdx so it can be used safely as a 64-bit pointer offset. The base address of the username string is loaded from var_28 into rax. add rax, rdx then advances that pointer by the current counter value, moving from one character of the username to the next on each pass. movzx eax, byte ptr [rax] dereferences that address and pulls out exactly one byte, the current character of the username, zero-extended into eax. movsx edx, al sign-extends that same byte value into edx.

add eax, edx then adds the loop counter to the ASCII value of the current character, producing a combined number. idiv [rbp+var_14] divides by the constant 0x3E (62 in decimal), which is exactly the length of the alphanumeric alphabet A-Za-z0-9 (26 lowercase + 26 uppercase + 10 digits). The quotient lands in rax and the remainder lands in rdx. It will always be a value between 0 and 61, making it a perfect index into the alphabet string.

The base address of the alphabet the (A-Za-z0-9 string) is loaded from var_8 into rax. The remainder from var_C is moved into rdx and add rax, rdx jumps exactly that many positions into the alphabet. movzx eax, byte ptr [rax] picks up the single character sitting at that position. mov [rdx], al writes it into the output key buffer. The counter increments via add [rbp+var_18], 1 and the loop repeats.

Loop Boundaries and Key Length

alt text

The loop checks two conditions before each iteration. The first check compares the counter against 0x17 (23 in decimal) and exits the loop if the counter exceeds it. This means the loop body runs for counter values 0 through 23, a total of 24 iterations, producing exactly a 24-character key. The second check, comparing the counter against 0xFE (254), is a dead branch that can never be reached because execution will have already jumped out of the loop at count 24.

The alphabet visible in the disassembly is a 62-character string containing all of A-Za-z0-9. The remainder from idiv, always between 0 and 61, is used directly as an index into this string to select the character written into the key buffer on that iteration.

Short-Username = Buffer-Over-Read

alt text

The loop is hardcoded to run exactly 24 times regardless of how long the username actually is. There is no bounds check or length guard on the input. When the username pointer walks off the end of the string it does not stop. It continues reading from whatever memory happens to be adjacent on the stack. In the debugger this is clearly visible: once the characters of the username are exhausted, RAX begins pulling in values like "OLORFBG=0", data that was never part of the input.

This garbage is fed through the same (ASCII + counter) % 62 math as normal characters, generating what looks like a valid key but one that is completely non-deterministic. Restarting the program or launching it outside the debugger shifts the stack layout, meaning the garbage data changes, and the tail end of the generated key changes with it. A username shorter than 24 characters therefore cannot produce a reliably reproducible key outside of a controlled debugger session.

Extracting the Key

alt text

To obtain a stable, predictable key, the username is set to exactly 24 characters in IDA's debugger options: helloworldhelloworld_007. This guarantees the loop reads exclusively from our controlled input for all 24 iterations and never spills into adjacent stack memory.

alt text

With a breakpoint set at the strcmp call, RAX points to the test dummy key I typed in (1234-5678-ABCD-EFGH-IJKL) and RDX points to the generated key. The debugger shows RDX resolving to dsnc7Q035x1b43AMPXZ87XOJ, which is the correct license key for the username helloworldhelloworld_007.

alt text

At this point both RAX and RDX resolve to dsnc7Q035x1b43AMPXZ87XOJ, confirming that is the key the program is expecting for this exact username.

alt text